package very.util.scalikejdbc

import java.sql.{PreparedStatement, ResultSet}

import org.json4s.{Formats, JObject, JValue}
import org.json4s.ext.JodaTimeSerializers
import org.json4s.native.JsonMethods.parse
import org.json4s.native.Serialization.write
import org.postgresql.util.PGobject
import scalikejdbc.{Binders, ParameterBinderWithValue}
import ws.very.util.json.JsonHelperWithDoubleMode

object BinderExt extends JsonHelperWithDoubleMode {
  override implicit val formats:Formats = org.json4s.DefaultFormats ++ JodaTimeSerializers.all

  implicit val listIntBinder: Binders[List[Int]] = new Binders[List[Int]] {
    override def apply(v: List[Int]): ParameterBinderWithValue = new ParameterBinderWithValue {
      override def value: Any = v
      override def apply(stmt: PreparedStatement, idx: Int): Unit = {
        stmt.setArray(idx, stmt.getConnection.createArrayOf("Int", v.toArray.map(_.asInstanceOf[AnyRef])))
      }
    }
    override def apply(rs: ResultSet, columnIndex: Int): List[Int] =
      rs.getArray(columnIndex).getArray.asInstanceOf[Array[Integer]].toList.map(_.toInt)

    override def apply(rs: ResultSet, columnLabel: String): List[Int] =
      rs.getArray(columnLabel).getArray.asInstanceOf[Array[Integer]].toList.map(_.toInt)
  }

  implicit val listShortBinder: Binders[List[Short]] = new Binders[List[Short]] {
    override def apply(v: List[Short]): ParameterBinderWithValue = new ParameterBinderWithValue {
      override def value: Any = v

      override def apply(stmt: PreparedStatement, idx: Int): Unit = {
        stmt.setArray(idx, stmt.getConnection.createArrayOf("int2", v.toArray.map(_.asInstanceOf[AnyRef])))
      }
    }
    override def apply(rs: ResultSet, columnIndex: Int): List[Short] =
      rs.getArray(columnIndex).getArray.asInstanceOf[Array[Short]].toList

    override def apply(rs: ResultSet, columnLabel: String): List[Short] =
      rs.getArray(columnLabel).getArray.asInstanceOf[Array[Short]].toList
  }

  implicit val listStringBinder: Binders[List[String]] = new Binders[List[String]] {
    override def apply(v: List[String]): ParameterBinderWithValue = new ParameterBinderWithValue {
      override def value: Any = v
      override def apply(stmt: PreparedStatement, idx: Int): Unit = {
        stmt.setArray(idx, stmt.getConnection.createArrayOf("Text", v.toArray.map(_.asInstanceOf[AnyRef])))
      }
    }
    override def apply(rs: ResultSet, columnIndex: Int): List[String] =
      rs.getArray(columnIndex).getArray.asInstanceOf[Array[String]].toList

    override def apply(rs: ResultSet, columnLabel: String): List[String] =
      rs.getArray(columnLabel).getArray.asInstanceOf[Array[String]].toList
  }

  def jsonbBinder[T](
      implicit forma: Formats = formats,
      mf: scala.reflect.Manifest[T]
  ): Binders[T] = new Binders[T] {
    override def apply(v: T): ParameterBinderWithValue = new ParameterBinderWithValue {
      private val jsonValue = write(v)(forma)
      override def value: Any = jsonValue

      override def apply(stmt: PreparedStatement, idx: Int): Unit = {
        val jsonObject = new PGobject
        jsonObject.setType("jsonb")
        jsonObject.setValue(jsonValue)
        stmt.setObject(idx, jsonObject)
      }
    }

    override def apply(rs: ResultSet, columnIndex: Int): T =
      parse(rs.getString(columnIndex)).extract[T](forma, mf)
    override def apply(rs: ResultSet, columnLabel: String): T =
      parse(rs.getString(columnLabel)).extract[T](forma, mf)
  }

  implicit val jValueBinder: Binders[JValue] = new Binders[JValue] {
    override def apply(rs: ResultSet, columnIndex: Int): JValue =  parse(rs.getString(columnIndex))

    override def apply(rs: ResultSet, columnLabel: String): JValue = parse(rs.getString(columnLabel))

    override def apply(valu: JValue): ParameterBinderWithValue = new ParameterBinderWithValue {
      private val jsonValue = toJson(valu)
      override def value: Any = {
        jsonValue
      }

      override def apply(stmt: PreparedStatement, idx: Int): Unit = {
        val jsonObject = new PGobject
        jsonObject.setType("jsonb")
        jsonObject.setValue(jsonValue)
        stmt.setObject(idx, jsonObject)
      }
    }
  }

  implicit val jObjectBinder: Binders[JObject] = new Binders[JObject]{
    override def apply(rs: ResultSet, columnIndex: Int): JObject = parse(rs.getString(columnIndex)).extract[JObject]

    override def apply(rs: ResultSet, columnLabel: String): JObject = parse(rs.getString(columnLabel)).extract[JObject]

    override def apply(valu: JObject): ParameterBinderWithValue =  new ParameterBinderWithValue {
      private val jsonValue = toJson(valu)
      override def value: Any = jsonValue

      override def apply(stmt: PreparedStatement, idx: Int): Unit = {
        val jsonObject = new PGobject
        jsonObject.setType("jsonb")
        jsonObject.setValue(jsonValue)
        stmt.setObject(idx, jsonObject)
      }
    }
  }

}
